﻿using System;
using System.Diagnostics;
using RaytracerLib.MathLib;
using YAXLib;
using TomanuExtensions;
using RaytracerLib.Rays;
using RaytracerLib.UVMappers;

namespace RaytracerLib.RenderableObjects
{
    public class TriangleObject : RenderableObject
    {
        [YAXNode("T1")]
        private Vector2 m_t1;

        [YAXNode("T2")]
        private Vector2 m_t2;

        [YAXNode("T3")]
        private Vector2 m_t3;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_n1;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_n2;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_n3;

        [YAXNode("N1")]
        private Vector3 m_local_n1;

        [YAXNode("N2")]
        private Vector3 m_local_n2;

        [YAXNode("N3")]
        private Vector3 m_local_n3;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_v1;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_v2;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Vector3 m_v3;

        [YAXNode("V1")]
        private Vector3 m_local_v1;

        [YAXNode("V2")]
        private Vector3 m_local_v2;

        [YAXNode("V3")]
        private Vector3 m_local_v3;

        private Vector3 m_tangent_x;
        private Vector3 m_tangent_y;

        public Vector3 Normal { get; private set; }

        public TriangleObject(Vector3 a_v1, Vector3 a_v2, Vector3 a_v3) :
            base(a_v2 - a_v1, Vector3.CrossProduct(a_v2 - a_v1, a_v3 - a_v1))
        {
            Name = "Triangle";
            m_uv_mapper = new TriangleUVMapper();

            Update(UpdateFlags.Matrices);

            m_v1 = a_v1;
            m_v2 = a_v2;
            m_v3 = a_v3;

            m_local_v1 = WorldToLocal * a_v1;
            m_local_v2 = WorldToLocal * a_v2;
            m_local_v3 = WorldToLocal * a_v3;

            Update(UpdateFlags.Normal | UpdateFlags.UVMapper);

            m_local_n1 = WorldToLocalNormal * Normal;
            m_local_n2 = WorldToLocalNormal * Normal;
            m_local_n3 = WorldToLocalNormal * Normal;

            Update(UpdateFlags.Tangents | UpdateFlags.BoundBox);
        }

        internal override Intersection GetIntersection(Intersection a_source_ray_intersection, Ray a_ray)
        {
            if ((a_source_ray_intersection != null) && (a_source_ray_intersection.SceneObject == this))
                return Scene.NoIntersection;

            if (OneSide)
            {
                if ((a_ray.Dir * Normal) > 0)
                    return Scene.NoIntersection;
            }

            var E1 = V2 - V1;
            var E2 = V3 - V1;

            Vector3 P = Vector3.CrossProduct(a_ray.Dir, E2);
            double a = E1 * P;

            if (a == 0)
                return Scene.NoIntersection;

            Vector3 T = a_ray.Start - V1;
            double u = (P * T) / a;

            if (u < 0)
            {
                if (u < -Constants.TRIANGLES_UV_INTERSECTION)
                    return Scene.NoIntersection;
                u = 0;
            }

            if (u > 1)
            {
                if (u > 1 + Constants.TRIANGLES_UV_INTERSECTION)
                    return Scene.NoIntersection;
                u = 1;
            }

            Vector3 Q = Vector3.CrossProduct(T, E1);
            double v = (a_ray.Dir * Q) / a;

            if (v < 0)
            {
                if (v < -Constants.TRIANGLES_UV_INTERSECTION)
                    return Scene.NoIntersection;
                v = 0;
            }
            if (u + v > 1.0)
                return Scene.NoIntersection;

            double dist = (Q * E2) / a;

            if (dist > 0)
            {
                bool back_hit = a < 0;

                if (back_hit && OneSide)
                    return Scene.NoIntersection;
                else
                {
                    Intersection result = new Intersection()
                    {
                        PrevIntersection = a_source_ray_intersection, 
                        SceneObject = this,
                        SourceRay = a_ray,
                        Dist = dist,
                        Scene = Scene,
                        BackHit = back_hit,
                        Pos = a_ray.HitPoint(dist),
                        BaricentricUV = new Vector2(u, v),
                        UV = BaricentricToUV(u, v)
                    };

                    if (UVMapper.Method == UVMappingMethod.Triangle)
                        result.UV = BaricentricToUV(u, v);

                    return result;
                }
            }
            else
                return Scene.NoIntersection;
        }

        private Vector2 BaricentricToUV(double a_u, double a_v)
        {
            return T1 * (1 - a_u - a_v) + T2 * a_u + T3 * a_v;
        }

        public static Vector3 BaricentricToPoint(Vector2 a_uv, Vector3 a_p0, Vector3 a_p1,
            Vector3 a_p2)
        {
            return a_p0 * (1 - a_uv.X - a_uv.Y) + a_p1 * a_uv.X + a_p2 * a_uv.Y;
        }

        /// <summary>
        /// Return area of triangle.
        /// </summary>
        /// <param name="a_v0"></param>
        /// <param name="a_v1"></param>
        /// <param name="a_v2"></param>
        /// <returns></returns>
        public static double Area(Vector3 a_v0, Vector3 a_v1, Vector3 a_v2)
        {
            return Vector3.CrossProduct(a_v1 - a_v0, a_v2 - a_v0).Length / 2;
        }

        /// <summary>
        /// Works only for points inside triangle.
        /// </summary>
        /// <param name="a_p"></param>
        /// <param name="a_v0"></param>
        /// <param name="a_v1"></param>
        /// <param name="a_v2"></param>
        /// <returns></returns>
        public static Vector2 PointToBaricentric(Vector3 a_p, Vector3 a_v0, Vector3 a_v1,
            Vector3 a_v2)
        {
            return new Vector2(Area(a_v2, a_v0, a_p) / Area(a_v0, a_v1, a_v2),
                               Area(a_v0, a_p, a_v1) / Area(a_v1, a_v0, a_v2));
        }

        internal override Vector3 GetShadeNormal(Intersection a_intersection)
        {
            double u = a_intersection.BaricentricUV.X;
            double v = a_intersection.BaricentricUV.Y;

            var r = new Vector3(N1 * (1 - u - v) + N2 * u + N3 * v).Normalized;

            if (a_intersection.BackHit)
                r = -r;

            return r;
        }

        public Vector3 V1
        {
            get
            {
                return m_v1;
            }
            set
            {
                m_local_v1 = WorldToLocal * value;
                Update(UpdateFlags.BoundBox | UpdateFlags.Normal | UpdateFlags.Tangents | UpdateFlags.Vertices);
            }
        }

        public Vector3 V2
        {
            get
            {
                return m_v2;
            }
            set
            {
                m_local_v2 = WorldToLocal * value;
                Update(UpdateFlags.BoundBox | UpdateFlags.Normal | UpdateFlags.Tangents | UpdateFlags.Vertices);
            }
        }

        public Vector3 V3
        {
            get
            {
                return m_v3;
            }
            set
            {
                m_local_v3 = WorldToLocal * value;
                Update(UpdateFlags.BoundBox | UpdateFlags.Normal | UpdateFlags.Tangents | UpdateFlags.Vertices);
            }
        }

        internal override Vector3 GetNormal(Intersection a_intersection)
        {
            if (a_intersection.BackHit)
                return -Normal;
            else
                return Normal;
        }

        protected override AABB GetBoundBox()
        {
            return new AABB(V1, V2, V3);
        }

        protected override AABB GetLocalBoundBox()
        {
            return new AABB(m_local_v1, m_local_v2, m_local_v3);
        }

        protected override void Update(UpdateFlags a_flags)
        {
            base.Update(a_flags);

            if (a_flags.HasFlag(UpdateFlags.Vertices))
            {
                m_v1 = LocalToWorld * m_local_v1;
                m_v2 = LocalToWorld * m_local_v2;
                m_v3 = LocalToWorld * m_local_v3;
            }

            if (a_flags.HasFlag(UpdateFlags.Normals))
            {
                m_n1 = (LocalToWorldNormal * m_local_n1).Normalized;
                m_n2 = (LocalToWorldNormal * m_local_n2).Normalized;
                m_n3 = (LocalToWorldNormal * m_local_n3).Normalized;
            }

            if (a_flags.HasFlag(UpdateFlags.Normal))
                Normal = Vector3.CrossProduct(V2 - V1, V3 - V1).Normalized;

            if (a_flags.HasFlag(UpdateFlags.Tangents))
            {
                Matrix4 m1 = new Matrix4(T2.X - T1.X, 0, T3.X - T1.X, 0,
                                         0, 1, 0, 0,
                                         T2.Y - T1.Y, 0, T3.Y - T1.Y, 0,
                                         0, 0, 0, 1).Inverted;

                Matrix4 m2 = new Matrix4(V2 - V1, Normal, V3 - V1);

                Matrix4 m3 = m2 * m1;

                m_tangent_x = (m3 * Vector3.XAXIS).Normalized;
                m_tangent_y = (m3 * Vector3.ZAXIS).Normalized;
            }
        }

        /// <summary>
        /// Was calculated in GetIntersection().
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal override Vector2 GetUV(Intersection a_intersection)
        {
            throw new InvalidOperationException("Was calculated in GetIntersection()");
        }

        public override string ToString()
        {
            return String.Format("Triangle: {0}", Name);
        }

        public Vector2 T1
        {
            get
            {
                return m_t1;
            }
            set
            {
                m_t1 = value;
                Update(UpdateFlags.Tangents);
            }
        }

        public Vector2 T2
        {
            get
            {
                return m_t2;
            }
            set
            {
                m_t2 = value;
                Update(UpdateFlags.Tangents);
            }
        }

        public Vector2 T3
        {
            get
            {
                return m_t3;
            }
            set
            {
                m_t3 = value;
                Update(UpdateFlags.Tangents);
            }
        }

        public Vector3 N1
        {
            get
            {
                return m_n1;
            }
            set
            {
                m_local_n1 = (WorldToLocalNormal * value).Normalized;
                Update(UpdateFlags.Normals);
            }
        }

        public Vector3 N2
        {
            get
            {
                return m_n2;
            }
            set
            {
                m_local_n2 = (WorldToLocalNormal * value).Normalized;
                Update(UpdateFlags.Normals);
            }
        }

        public Vector3 N3
        {
            get
            {
                return m_n3;
            }
            set
            {
                m_local_n3 = (WorldToLocalNormal * value).Normalized;
                Update(UpdateFlags.Normals);
            }
        }

        internal override Vector3 GetUVW(Intersection a_intersection)
        {
            double minx = DoubleExtensions.Min(m_local_v1.X, m_local_v2.X, m_local_v3.X);
            double miny = DoubleExtensions.Min(m_local_v1.Y, m_local_v2.Y, m_local_v3.Y);
            double minz = DoubleExtensions.Min(m_local_v1.Z, m_local_v2.Z, m_local_v3.Z);

            double maxx = DoubleExtensions.Max(m_local_v1.X, m_local_v2.X, m_local_v3.X);
            double maxy = DoubleExtensions.Max(m_local_v1.Y, m_local_v2.Y, m_local_v3.Y);
            double maxz = DoubleExtensions.Max(m_local_v1.Z, m_local_v2.Z, m_local_v3.Z);

            double deltax = maxx - minx;
            double deltay = maxy - miny;
            double deltaz = maxz - minz;

            Vector3 uvw = base.GetUVW(a_intersection);

            return new Vector3(
                (uvw.X - minx) / ((deltax != 0) ? deltax : 1),
                (uvw.Y - miny) / ((deltay != 0) ? deltay : 1),
                (uvw.Z - minz) / ((deltaz != 0) ? deltaz : 1));
        }

        internal override void GetTangents(Intersection a_intersection,
            out Vector3 a_tangent_x, out Vector3 a_tangent_y)
        {
            a_tangent_x = m_tangent_x;
            a_tangent_y = m_tangent_y;
        }

        public override void ScaleAbsolute(double a_scale)
        {
            m_local_v1 = a_scale * m_local_v1;
            m_local_v2 = a_scale * m_local_v2;
            m_local_v3 = a_scale * m_local_v3;

            base.ScaleAbsolute(a_scale);
        }
    }
}